import {Router, Express} from "express";
import {ReflectMeta} from "../utils";
import {Request, Response, NextFunction} from "express";
import HttpErrorException from "../exceptions/HttpErrorException";
import {ValidateObjectKeys} from "./common";
import {createRecord} from "../record";
import HttpSuccessExpression from "../exceptions/HttpSuccessExpression";

const getHandleCallbackParams = (
  requestParams: { target: string, attr: { attrName: string, subAttr: ValidateObjectKeys[] }, index: number }[],
  req: Request,
  res: Response,
  next: NextFunction
) => {
  // 需要验证参数的函数
  // 不需要参数验证的函数
  if (!requestParams || !requestParams?.length) return;
  let args: any[] = [];
  const paramsLost: string[] = [];
  const paramsHandleType = {
    "body": (paramName: string) => req.body[paramName],
    "query": (paramName: string) => req.query[paramName]
  };


  const paramsValueList = requestParams.map(param => {
    const paramValue = paramsHandleType[(param.target as keyof (typeof paramsHandleType))](param.attr.attrName);
    let paramsLostStr: string = "";
    if (typeof paramValue === "object") {
      paramsLostStr += `对象${param.attr.attrName}`;
    } else {
      paramsLostStr += `参数${param.attr.attrName}`;
    }
    if (!paramValue) {
      paramsLost.push(paramsLostStr);
    } else if (paramValue && typeof paramValue === "object") {
      const subStr = validateParamsObjAttr(paramValue, param.attr.subAttr);
      if (subStr) {
        paramsLostStr += validateParamsObjAttr(paramValue, param.attr.subAttr);
        paramsLost.push(paramsLostStr);
      } else {
        args.unshift(paramValue);
      }
    } else {
      args.unshift(paramValue);
    }
    return paramValue;
  });

  const checkRequiredParams = paramsValueList.every(
    value => !!value
  );

  if (!checkRequiredParams || paramsLost.length) {
    // 必要参数丢失
    let paramsLostStr: string = "";
    paramsLost.forEach(str => {
      paramsLostStr += str + "、";
    });
    paramsLostStr = paramsLostStr.substring(0, paramsLostStr.length - 1);
    const error = new HttpErrorException(-1, `缺少必要参数， ${paramsLostStr} `);
    next(error);
    return error;
  }
  return args;
};

const validateParamsObjAttr = (target: Object, attrs: ValidateObjectKeys[]) => {
  let result: string[] = [];
  for (let i = 0; i < attrs.length; i++) {
    if (attrs[i].required && !(target as any)[attrs[i].name]) {
      result.push(attrs[i].name);
    }
  }
  return `${result.length ? "属性" : ""}${result.join("、")}`;
};

export const registerRouter = (app: Express, controllers: any[]) => {
  const router: any = Router();
  controllers?.forEach(constructor => {
    let classPath = Reflect.getMetadata(ReflectMeta.PATH_META, constructor);
    const classMethodKeys = Object.getOwnPropertyNames(constructor.prototype).filter(methodName => methodName !== "constructor");

    classMethodKeys?.forEach(methodName => {
      const requestMethod = Reflect.getMetadata(ReflectMeta.METHOD_META, constructor.prototype, methodName);
      const requestPath = Reflect.getMetadata(ReflectMeta.PATH_META, constructor.prototype, methodName);
      const requestParams = Reflect.getMetadata(ReflectMeta.PARAMS_META, constructor.prototype, methodName);
      const descriptor = Reflect.getMetadata(ReflectMeta.DESCRIPTOR_META, constructor.prototype, methodName);
      const recordMeta = Reflect.getMetadata(ReflectMeta.RECORD_META, constructor.prototype, methodName);

      let routerHandlerFn = async (req: Request, res: Response, next: NextFunction) => {
        const params = getHandleCallbackParams(requestParams, req, res, next);
        if (params instanceof HttpErrorException) {
          return;
        }
        try {
          const method = descriptor.value;
          let result: HttpErrorException | HttpSuccessExpression;
          if (!params) {
            result = await method();
          } else {
            result = await method(...(params.sort() as any[]));
          }
          if (result instanceof HttpErrorException) {
            // 操作数据库失败
            const {status, message, errors} = result as HttpErrorException;
            return res.send({code: status, message, errors});
          }
          const {record} = result;
          record && recordMeta?.recordModel && createRecord(recordMeta.recordModel, record);
          return res.send({
            code: result.code,
            message: result.message,
            data: result.data
          });
        } catch (error) {
          next(error);
        }
      };

      let routerUrl = classPath + requestPath;
      router[requestMethod].apply(router, [routerUrl, routerHandlerFn]);
    });


  });
  app.use(router);
};